﻿#define USE_SHADOW

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Effects;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;

namespace ColorDialogEx
{
	internal class ColorDialogExLogic
	{
		/// <summary>
		/// Color is changed by user
		/// </summary>
		public event EventHandler<ColorEventArgs> ColorChanged;
		protected virtual void OnColorChanged(ColorEventArgs e)
		{
			EventHandler<ColorEventArgs> handler = ColorChanged;
			if (handler != null) handler(this, e);
		}

		private Canvas HostingCanvas;

		#region Color things
		// ------------------------------------------
		/// <summary>
		/// Get or set Hue value
		/// </summary>
		public double H 
		{
			get { return Math.Round(_h, 2); }
			set
			{
				if (_h != value)
				{
					_h = value;
					Move_HexDirector(value, HexPntC);

					ColorEventArgs e = new ColorEventArgs();
					e.ColorRGB = ColorRGB;
					e.ColorARGB = Color.FromArgb(A, e.ColorRGB.R, e.ColorRGB.G, e.ColorRGB.B);
					OnColorChanged(e);
				}
			}
		}

		/// <summary>
		/// Get or set Saturation value
		/// </summary>
		public double S
		{
			get
			{
				return Math.Round(_s, 4);
			}
			set
			{
				if (_s != value)
				{
					_s = value;
					Move_SatDirector(value);

					ColorEventArgs e = new ColorEventArgs();
					e.ColorRGB = ColorRGB;
					e.ColorARGB = Color.FromArgb(A, e.ColorRGB.R, e.ColorRGB.G, e.ColorRGB.B);
					OnColorChanged(e);
				}
			}
		}

		/// <summary>
		/// Get or set Lightness value
		/// </summary>
		public double L 
		{
			get
			{
				return Math.Round(_l, 4);
			}
			set
			{
				if (_l != value)
				{
					_l = value;
					Move_TriColorMark(value);
					Redraw_TriangleMarkers();
					Redraw_Alphator();

					ColorEventArgs e = new ColorEventArgs();
					e.ColorRGB = ColorRGB;
					e.ColorARGB = Color.FromArgb(A, e.ColorRGB.R, e.ColorRGB.G, e.ColorRGB.B);
					OnColorChanged(e);
				}
			}
		}

		/// <summary>
		/// Get or set Alpha value
		/// </summary>
		public byte A
		{
			get { return _a; }
			set
			{
				if (_a != value)
				{
					_a = value;
					Move_AlphDirector(value);

					ColorEventArgs e = new ColorEventArgs();
					e.ColorRGB = ColorRGB;
					e.ColorARGB = Color.FromArgb(A, e.ColorRGB.R, e.ColorRGB.G, e.ColorRGB.B);
					OnColorChanged(e);
				}
			}
		}

		/// <summary>
		/// Get or set color value w/o specific Alpha
		/// </summary>
		public Color ColorRGB
		{
			get
			{
				Color C = HSLConverter.HslToRgb(new HSL(H, S, L));
				return Color.FromArgb(255, C.R, C.G, C.B);
			}
			set
			{
				HSL hsl = HSLConverter.RgbToHsl(value);
				_h = hsl.H;
				_s = hsl.S;
				_l = hsl.L;
				_a = 255;

				Move_HexDirector(_h, HexPntC);
				Move_SatDirector(_s);
				Move_TriColorMark(_l);
				Redraw_TriangleMarkers();
				Redraw_Alphator();
				Move_AlphDirector(_a);

				ColorEventArgs e = new ColorEventArgs();
				e.ColorRGB = value;
				e.ColorARGB = Color.FromArgb(A, e.ColorRGB.R, e.ColorRGB.G, e.ColorRGB.B);
				OnColorChanged(e);
			}
		}

		/// <summary>
		/// Get or set color value with specific Alpha
		/// </summary>
		public Color ColorARGB
		{
			get
			{
				Color C = HSLConverter.HslToRgb(new HSL(H, S, L));
				return Color.FromArgb(A, C.R, C.G, C.B);
			}
			set
			{
				HSL hsl = HSLConverter.RgbToHsl(value);
				_h = hsl.H;
				_s = hsl.S;
				_l = hsl.L;
				_a = value.A;

				Move_HexDirector(_h, HexPntC);
				Move_SatDirector(_s);
				Move_TriColorMark(_l);
				Redraw_TriangleMarkers();
				Redraw_Alphator();
				Move_AlphDirector(_a);

				ColorEventArgs e = new ColorEventArgs();
				e.ColorRGB = value;
				e.ColorARGB = Color.FromArgb(A, e.ColorRGB.R, e.ColorRGB.G, e.ColorRGB.B);
				OnColorChanged(e);
			}
		}

		private double _h = 0;
		private double _l = 0.5;
		private double _s = 1;
		private byte _a = 255;
		// ------------------------------------------
		#endregion


		#region Hexagon things
		// ------------------------------------------
		private Point HexPntC; // center point
		private Point HexPnt1, HexPnt2, HexPnt3, HexPnt4, HexPnt5, HexPnt6; // hexagon points
		private Path HexPath; // hexagon graphical object
		private double HexInnerRadius; // radius of inner circle

		private Path HexMarkPath; // small triangle markers inside hexagon
		private PolyLineSegment HexDirectorLine; // director line inside hexagon
		private Point HexDirectorPoint = new Point(0, 0); // target direction of line
		// ------------------------------------------
		#endregion


		#region Triangle things
		// ------------------------------------------
		private Canvas TriCanvas; // canvas for main triangle and markers
		private RotateTransform TriCanvasTrans; // rotate transformation for triangle canvas

		private Point  TriPnt1, TriPnt2, TriPnt3; // main triangle points
		private PathFigure TriFigure; // cashed figure for markers creation

		private Path		TriPath;  // main triangle graphicsl object
		private double		TriSide;  // lenght of the side of main triangle
		private double		TriHeight; // height of main triangle
		private ImageBrush	TriIBrush; // background brush for main triangle
		private LinearGradientBrush TriBrushAlt; // alternative draw background brush for main triangle

		private Path		TriMarkPath1; // object of triangle marker 1
		private Path		TriMarkPath2; // object of triangle marker 2
		private Path		TriMarkPath3; // object of triangle marker 3
		private Path		TriMarkPath4; // object of triangle marker 4
		private Path		TriMarkPath5; // object of triangle marker 5

		private Path		TriColorMark; // marker of color inside triangle
		private TranslateTransform TriColorMarkTrans; // transformation for color markers

		private TranslateTransform TriMarkTrans1, TriMarkTrans2, TriMarkTrans3, TriMarkTrans4, TriMarkTrans5; // for small 3 markers of color triangle
		// ------------------------------------------
		#endregion


		#region Alpha and Saturation things
		// ------------------------------------------
		private Path SatPath; // saturator rectangle
		private Point SatPnt1; // saturation rect point 1
		private Point SatPnt2; // saturation rect point 1
		private LinearGradientBrush SatBrush; // saturator background

		private Path SatDirectorPath; // line for saturator
		private TranslateTransform SatDirTrans; // translation for saturation director

		private Path AlphPath; // alphator rectangle
		private Path AlphPathB; // background alphator rectangle
		private Point AlphPnt1; // alphator rect point 1
		private Point AlphPnt2; // alphator rect point 1

		private Path AlphDirectorPath; // line for alphator
		private TranslateTransform AlphDirTrans; // translation for alpha director

		private LinearGradientBrush AlphBrush; // alphator background
		private ImageBrush AlphBrushB; // for background alphator
		// ------------------------------------------
		#endregion


		public ColorDialogExLogic(Canvas HostingCanvas)
		{
			this.HostingCanvas = HostingCanvas;
			Recreate_All();
		}

		public ColorDialogExLogic(Canvas HostingCanvas, Color ColorRGB)
		{
			this.HostingCanvas = HostingCanvas;
			Recreate_All();
			ColorARGB = ColorRGB;
		}

		public void RefreshResize()
		{
			Recreate_All();
		}

		private void Recreate_All()
		{
			if (HostingCanvas.ActualWidth < 16 || HostingCanvas.ActualHeight < 16)
				return;

			HostingCanvas.SnapsToDevicePixels = false;

			//------------------------------------------begin new ---------------------
			HostingCanvas.Children.Clear();

			//------------------------------------------base points for hexagon -------
			Recreate_BasePoints(HostingCanvas);

			//------------------------------------------hexagon + director line --------
			Recreate_Hexagon(HostingCanvas);

			//------------------------------------------triangle -----------------------
			Recreate_Triangle(HostingCanvas);

			//------------------------------------------hexagon markers ----------------
			Recreate_HexMarkers();

			//------------------------------------------color marker -------------------
			Recreate_ColorMarker();

			//------------------------------------------triangle markers ---------------
			Recreate_TriangleMarkers();

			//------------------------------------------saturation edit ----------------
			Recreate_Saturator(HostingCanvas);

			//------------------------------------------alpha channel edit -------------
			Recreate_Alphator(HostingCanvas);

			//------------------------------------------initialize positions -----------
			Move_HexDirector(_h, HexPntC);
			Move_TriColorMark(_l);
			Move_SatDirector(_s);
			Move_AlphDirector(_a);

			//------------------------------------------hexagon background redraw ------
			Redraw_Hexagon(HostingCanvas);

			//------------------------------------------triangle background redraw -----
			Redraw_TriangleAlt();

			//------------------------------------------markers redraw -----------------
			Redraw_TriangleMarkers();

			//------------------------------------------saturation edit redraw ---------
			Redraw_Saturator();

			//------------------------------------------alpha edit redraw --------------
			Redraw_AlphatorB();
			Redraw_Alphator();


			//------------------------------------------hexagon events -----------------
			#region Hexagon events

			bool mouseGearCaptured = false;

			HexPath.MouseDown += (o, args) =>
						{
							HexPath.CaptureMouse();
							mouseGearCaptured = true;
							args.Handled = true;

							Point mousePoint = args.GetPosition(HexPath);
							_h = GetAngle(mousePoint, HexPntC);
							Move_HexDirector(mousePoint, HexInnerRadius / 3);

							ColorEventArgs e = new ColorEventArgs();
							e.ColorRGB = ColorRGB; 
							e.ColorARGB = Color.FromArgb(A, e.ColorRGB.R, e.ColorRGB.G, e.ColorRGB.B);
							OnColorChanged(e);
						};
			HexPath.MouseUp += (o, args) =>
						{
							HexPath.ReleaseMouseCapture();
							mouseGearCaptured = false;
							args.Handled = true;
						};
			HexPath.MouseMove += (o, args) =>
						{
							if (!mouseGearCaptured) return;
							args.Handled = true;

							Point mousePoint = args.GetPosition(HexPath);
							_h = GetAngle(mousePoint, HexPntC);
							Move_HexDirector(mousePoint, HexInnerRadius / 3);

							ColorEventArgs e = new ColorEventArgs();
							e.ColorRGB = ColorRGB;
							e.ColorARGB = Color.FromArgb(A, e.ColorRGB.R, e.ColorRGB.G, e.ColorRGB.B);
							OnColorChanged(e);
						};

			#endregion


			//------------------------------------------hexagon markers events ---------
			#region Hexagon markers events

			HexMarkPath.MouseDown += (o, args) =>
						{
							Point pos = args.GetPosition(TriCanvas);

							double a = GetAngle(pos, new Point(TriCanvas.Width / 2d, TriCanvas.Height / 2d)) - 90;

							double anew = Math.Round(a / 30d) * 30;

							double canvasAngle = H;

							anew = canvasAngle + anew;
							
							H = anew;
						};

			#endregion


			//------------------------------------------triangle markers events --------
			#region Triangle markers events

			TriMarkPath1.MouseDown += (o, args) =>
				        {
					        double X = TriMarkTrans1.X;
							double P = (X + TriSide / 2d) / TriSide;
							L = P;
						};
			TriMarkPath2.MouseDown += (o, args) =>
						{
							double X = TriMarkTrans2.X;
							double P = (X + TriSide / 2d) / TriSide;
							L = P;
						};
			TriMarkPath3.MouseDown += (o, args) =>
						{
							double X = TriMarkTrans3.X;
							double P = (X + TriSide / 2d) / TriSide;
							L = P;
						};
			TriMarkPath4.MouseDown += (o, args) =>
						{
							double X = TriMarkTrans4.X;
							double P = (X + TriSide / 2d) / TriSide;
							L = P;
						};
			TriMarkPath5.MouseDown += (o, args) =>
						{
							double X = TriMarkTrans5.X;
							double P = (X + TriSide / 2d) / TriSide;
							L = P;
						};

			#endregion


			//------------------------------------------triangle events ----------------
			#region Triangle events

			bool mouseTriangleCaptured = false;

			TriPath.MouseDown += (o, args) =>
						{
							TriPath.CaptureMouse();
							mouseTriangleCaptured = true;
							args.Handled = true;


							Point mousePoint = args.GetPosition(TriCanvas);
							Move_TriColorMark(mousePoint);

							Redraw_TriangleMarkers();
							Redraw_Alphator();

							ColorEventArgs e = new ColorEventArgs();
							e.ColorRGB = ColorRGB;
							e.ColorARGB = Color.FromArgb(A, e.ColorRGB.R, e.ColorRGB.G, e.ColorRGB.B);
							OnColorChanged(e);
						};

			TriPath.MouseUp += (o, args) =>
						{
							TriPath.ReleaseMouseCapture();
							mouseTriangleCaptured = false;
							args.Handled = true;
						};

			TriPath.MouseMove += (o, args) =>
						{
							if (!mouseTriangleCaptured)
								return;

							Point mousePoint = args.GetPosition(TriCanvas);
							Move_TriColorMark(mousePoint);

							Redraw_TriangleMarkers();
							Redraw_Alphator();

							ColorEventArgs e = new ColorEventArgs();
							e.ColorRGB = ColorRGB;
							e.ColorARGB = Color.FromArgb(A, e.ColorRGB.R, e.ColorRGB.G, e.ColorRGB.B);
							OnColorChanged(e);
						};

			TriPath.MouseWheel += (o, args) =>
						{

							double a = GetAngle(HexDirectorLine.Points[0], HexPntC);
							double anew = args.Delta > 0 ? a + 30 : a - 30;
							H = anew;
						};

			#endregion


			//------------------------------------------saturator events ---------------
			#region Saturator events

			bool mouseSatCaptured = false;

			SatPath.MouseDown += (o, args) =>
						{
							SatPath.CaptureMouse();
							mouseSatCaptured = true;
							args.Handled = true;

							Point mousePoint = args.GetPosition(HostingCanvas);
							Move_SatDirector(mousePoint);

							ColorEventArgs e = new ColorEventArgs();
							e.ColorRGB = ColorRGB;
							e.ColorARGB = Color.FromArgb(A, e.ColorRGB.R, e.ColorRGB.G, e.ColorRGB.B);
							OnColorChanged(e);
						};
			SatPath.MouseUp += (o, args) =>
						{
							SatPath.ReleaseMouseCapture();
							mouseSatCaptured = false;
							args.Handled = true;
						};
			SatPath.MouseMove += (o, args) =>
						{
							if (!mouseSatCaptured) return;
							args.Handled = true;

							Point mousePoint = args.GetPosition(HostingCanvas);
							Move_SatDirector(mousePoint);

							ColorEventArgs e = new ColorEventArgs();
							e.ColorRGB = ColorRGB;
							e.ColorARGB = Color.FromArgb(A, e.ColorRGB.R, e.ColorRGB.G, e.ColorRGB.B);
							OnColorChanged(e);
						};

			#endregion


			//------------------------------------------alphator events ---------------
			#region Alphator events

			bool mouseAlphCaptured = false;

			AlphPath.MouseDown += (o, args) =>
						{
							AlphPath.CaptureMouse();
							mouseAlphCaptured = true;
							args.Handled = true;

							Point mousePoint = args.GetPosition(HostingCanvas);
							Move_AlphDirector(mousePoint);

							ColorEventArgs e = new ColorEventArgs();
							e.ColorRGB = ColorRGB;
							e.ColorARGB = Color.FromArgb(A, e.ColorRGB.R, e.ColorRGB.G, e.ColorRGB.B);
							OnColorChanged(e);
						};
			AlphPath.MouseUp += (o, args) =>
						{
							AlphPath.ReleaseMouseCapture();
							mouseAlphCaptured = false;
							args.Handled = true;
						};
			AlphPath.MouseMove += (o, args) =>
						{
							if (!mouseAlphCaptured) return;
							args.Handled = true;

							Point mousePoint = args.GetPosition(HostingCanvas);
							Move_AlphDirector(mousePoint);

							ColorEventArgs e = new ColorEventArgs();
							e.ColorRGB = ColorRGB;
							e.ColorARGB = Color.FromArgb(A, e.ColorRGB.R, e.ColorRGB.G, e.ColorRGB.B);
							OnColorChanged(e);
						};

			#endregion


			//------------------------------------------shadow FX ? --------------------
			#if USE_SHADOW
			DropShadowEffect shadowEffect = new DropShadowEffect();
			shadowEffect.ShadowDepth = 2;
			shadowEffect.BlurRadius = 4;
			shadowEffect.Opacity = 0.4;

			HexPath.Effect = shadowEffect;
			TriPath.Effect = shadowEffect;
			SatPath.Effect = shadowEffect;
			AlphPath.Effect = shadowEffect;
			#endif
		}

		/// <summary>
		/// Calculate base points for every future calculations and figures creation
		/// </summary>
		/// <param name="hoster">UIElement where to draw</param>
		private void Recreate_BasePoints(Canvas hoster)
		{
			double radius = (int)Math.Min(hoster.ActualWidth / 2d, hoster.ActualHeight / 2d - hoster.ActualHeight * 0.04);
			double h1 = (int)((Math.Sqrt(3) / 2d) * radius);
			HexInnerRadius = (int)(h1 - (h1 * 0.06));

			HexPntC = new Point((int)(hoster.ActualWidth / 2d), (int)(hoster.ActualHeight / 2d));

			HexPnt1 = new Point(HexPntC.X + radius, HexPntC.Y);
			HexPnt2 = new Point((int)(HexPntC.X + radius / 2d), HexPntC.Y - h1);
			HexPnt3 = new Point((int)(HexPntC.X - radius / 2d), HexPntC.Y - h1);
			HexPnt4 = new Point(HexPntC.X - radius, HexPntC.Y);
			HexPnt5 = new Point((int)(HexPntC.X - radius / 2d), HexPntC.Y + h1);
			HexPnt6 = new Point((int)(HexPntC.X + radius / 2d), HexPntC.Y + h1);
		}

		/// <summary>
		/// Recreate main hexagon as Hue-wheel color selection
		/// </summary>
		private void Recreate_Hexagon(Canvas hoster)
		{
			//--------- HEX  ----------------------------------------------------
			PathFigure hexF = new PathFigure();
			hexF.StartPoint = HexPnt1;
			hexF.Segments.Add(new PolyLineSegment(new[] {HexPnt2, HexPnt3, HexPnt4, HexPnt5, HexPnt6, HexPnt1}, true));
			hexF.IsClosed = true;

			PathGeometry hexG = new PathGeometry();
			hexG.Figures.Add(hexF);

			Path hexPath = new Path();
			hexPath.Data = hexG;
			hexPath.StrokeThickness = 2;
			hexPath.Stroke = new SolidColorBrush(Colors.Black);
			hexPath.Fill = new SolidColorBrush(Colors.Chocolate);
			//hoster.Children.Add(hexPath); /* For visual test */


			//--------- Ellipse ----------------------------------------------------
			EllipseGeometry elG = new EllipseGeometry(new Point(HexPntC.X, HexPntC.Y), HexInnerRadius, HexInnerRadius);
			Path elPath = new Path();
			elPath.Data = elG;
			elPath.StrokeThickness = 2;
			elPath.Stroke = new SolidColorBrush(Colors.Black);
			elPath.Fill = new SolidColorBrush(Colors.Chocolate);
			//hoster.Children.Add(elPath); /* For visual test */


			//--------- Combined Hex ----------------------------------------------------
			HexPath = new Path();
			HexPath.Data = new CombinedGeometry(GeometryCombineMode.Exclude, hexPath.Data, elPath.Data);
			HexPath.StrokeThickness = 0.5;
			HexPath.Stroke = new SolidColorBrush(Colors.Gray);
			//GearPath.Fill = new SolidColorBrush(Colors.Chocolate); /* For visual test */

			hoster.Children.Add(HexPath);
			HexPath.SnapsToDevicePixels = true;


			//--------- Hex director Line  -----------------------------------------------
			Path originalLinePath = new Path();
			if (HexDirectorPoint.X == 0 && HexDirectorPoint.Y == 0)
				HexDirectorPoint = new Point(HexPntC.X, 0);
			HexDirectorLine = new PolyLineSegment(new[] {HexDirectorPoint, new Point(HexPntC.X + 0.1, HexPntC.Y + 0.1)}, true);
			PathGeometry lineG = new PathGeometry(new[]
				                                      {
					                                      new PathFigure(HexPntC, new[]
						                                                              {
							                                                              HexDirectorLine
						                                                              },
					                                                     true)
				                                      });
			originalLinePath.Data = lineG;
			originalLinePath.StrokeThickness = 1;
			originalLinePath.Stroke = new SolidColorBrush(Colors.Black);
			//hoster.Children.Add(originalLinePath); /* For visual test */


			//--------- Combined Hex director Line  ---------------------------------------
			Path GearLinePath = new Path();
			GearLinePath.Data = new CombinedGeometry(GeometryCombineMode.Exclude, originalLinePath.Data, elPath.Data);
			GearLinePath.Data = new CombinedGeometry(GeometryCombineMode.Intersect, GearLinePath.Data, hexPath.Data);
			GearLinePath.StrokeThickness = 0.5;
			GearLinePath.Stroke = new SolidColorBrush(Colors.Black);
			hoster.Children.Add(GearLinePath);
			GearLinePath.SnapsToDevicePixels = false;
		}

		/// <summary>
		/// Recreate main triangle inside hexagon
		/// </summary>
		private void Recreate_Triangle(Canvas hoster)
		{
			TriSide = HexInnerRadius / (Math.Sqrt(3d) / 3d);
			TriHeight = (Math.Sqrt(3d) / 2d) * TriSide;

			TriCanvas = new Canvas(); // background canvas for main triangle
			TriCanvas.Width = HexInnerRadius * 2;
			TriCanvas.Height = HexInnerRadius * 2;

			// 3 points of main triangle
			TriPnt1 = new Point(HexInnerRadius, 0);
			TriPnt2 = new Point(TriPnt1.X - TriSide / 2d, TriHeight);
			TriPnt3 = new Point(TriPnt1.X + TriSide / 2d, TriHeight);

			TriFigure = new PathFigure();
			TriFigure.StartPoint = TriPnt1;
			TriFigure.Segments.Add(new PolyLineSegment(new[] {TriPnt2, TriPnt3, TriPnt1}, true));
			TriFigure.IsClosed = true;

			PathGeometry triG = new PathGeometry();
			triG.Figures.Add(TriFigure);

			TriPath = new Path();
			TriPath.Data = triG;
			TriPath.StrokeThickness = 0.5;
			TriPath.Stroke = new SolidColorBrush(Colors.Gray);

			TriBrushAlt = new LinearGradientBrush(); // alternative- Redraw_TriangleAlt()

			TriIBrush = new ImageBrush();
			TriIBrush.Stretch = Stretch.None;
			TriIBrush.TileMode = TileMode.Tile;
			TriPath.Fill = TriIBrush;
			//hoster.Background = triIB;  /* For visual test */

			TriCanvas.Children.Add(TriPath);
			hoster.Children.Add(TriCanvas);
			//TriCanvas.Background = new SolidColorBrush(Colors.Red);  /* For visual test */

			TriPath.SnapsToDevicePixels = false;

			TranslateTransform center = new TranslateTransform(HexPntC.X - TriCanvas.Width / 2d, HexPntC.Y - TriCanvas.Height / 2d);
			TriCanvas.LayoutTransform = center;

			TriCanvasTrans = new RotateTransform();
			TriCanvasTrans.Angle = GetAngle(HexDirectorPoint, HexPntC) * -1 + 90;
			TriCanvasTrans.CenterX = TriCanvas.Width / 2d;
			TriCanvasTrans.CenterY = TriCanvas.Height / 2d;

			TransformGroup group = new TransformGroup();
			group.Children.Add(TriCanvasTrans);
			group.Children.Add(center);

			TriCanvas.RenderTransform = group;
		}

		/// <summary>
		/// Recreate markers inside hexagon
		/// </summary>
		private void Recreate_HexMarkers()
		{
			PathGeometry markG1 = new PathGeometry();
			markG1.Figures.Add(TriFigure);
			RotateTransform t1 = new RotateTransform(30, TriCanvas.Width / 2d, TriCanvas.Height / 2d);
			markG1.Transform = t1;

			PathGeometry markG2 = new PathGeometry();
			markG2.Figures.Add(TriFigure);
			RotateTransform t2 = new RotateTransform(60, TriCanvas.Width / 2d, TriCanvas.Height / 2d);
			markG2.Transform = t2;

			PathGeometry markG3 = new PathGeometry();
			markG3.Figures.Add(TriFigure);
			RotateTransform t3 = new RotateTransform(90, TriCanvas.Width / 2d, TriCanvas.Height / 2d);
			markG3.Transform = t3;

			Path markPath1 = new Path();
			markPath1.Data = markG1;
			markPath1.StrokeThickness = 1;
			markPath1.Stroke = Brushes.Black;
			//hoster.Children.Add(markPath1); /* For visual test */

			Path markPath2 = new Path();
			markPath2.Data = markG2;
			markPath2.StrokeThickness = 1;
			markPath2.Stroke = Brushes.Black;
			//hoster.Children.Add(markPath2); /* For visual test */

			Path markPath3 = new Path();
			markPath3.Data = markG3;
			markPath3.StrokeThickness = 1;
			markPath3.Stroke = Brushes.Black;
			//hoster.Children.Add(markPath3); /* For visual test */

			EllipseGeometry triElG = new EllipseGeometry(new Point(TriCanvas.Width / 2d, TriCanvas.Height / 2d),
			                                             HexInnerRadius - (HexInnerRadius * 0.08),
			                                             HexInnerRadius - (HexInnerRadius * 0.08));
			Path triElPath = new Path();
			triElPath.Data = triElG;
			triElPath.StrokeThickness = 1;

			HexMarkPath = new Path();
			HexMarkPath.Data = new CombinedGeometry(GeometryCombineMode.Union, markPath1.Data, markPath2.Data);
			HexMarkPath.Data = new CombinedGeometry(GeometryCombineMode.Union, HexMarkPath.Data, markPath3.Data);
			HexMarkPath.Data = new CombinedGeometry(GeometryCombineMode.Exclude, HexMarkPath.Data, triElPath.Data);
			HexMarkPath.StrokeThickness = 0.5;
			HexMarkPath.Stroke = Brushes.DarkGray;
			HexMarkPath.Fill = Brushes.LightGray;

			TriCanvas.Children.Add(HexMarkPath);
			HexMarkPath.SnapsToDevicePixels = true;
		}

		/// <summary>
		/// Recreate 3 markers at bottom of main triangle
		/// </summary>
		private void Recreate_TriangleMarkers()
		{
			double triMarkHeight = (HexInnerRadius * 0.08);
			double triMarkSide = triMarkHeight / (Math.Sqrt(3d) / 2d);

			Point triMarkPnt1 = new Point(TriPnt1.X, TriHeight);
			Point triMarkPnt2 = new Point(TriPnt1.X - triMarkSide / 2d, triMarkPnt1.Y + triMarkHeight);
			Point triMarkPnt3 = new Point(TriPnt1.X + triMarkSide / 2d, triMarkPnt1.Y + triMarkHeight);

			PathGeometry triMark1G = new PathGeometry();
			triMark1G.Figures.Add(new PathFigure(triMarkPnt1,
			                                     new PathSegment[]
				                                     {new PolyLineSegment(new[] {triMarkPnt2, triMarkPnt3, triMarkPnt1}, true)}, true));

			PathGeometry triMark2G = new PathGeometry();
			triMark2G.Figures.Add(new PathFigure(triMarkPnt1,
												 new PathSegment[] { new PolyLineSegment(new[] { triMarkPnt2, triMarkPnt3, triMarkPnt1 }, true) }, true));

			PathGeometry triMark3G = new PathGeometry();
			triMark3G.Figures.Add(new PathFigure(triMarkPnt1,
												 new PathSegment[] { new PolyLineSegment(new[] { triMarkPnt2, triMarkPnt3, triMarkPnt1 }, true) }, true));

			PathGeometry triMark4G = new PathGeometry();
			triMark4G.Figures.Add(new PathFigure(triMarkPnt1,
												 new PathSegment[] { new PolyLineSegment(new[] { triMarkPnt2, triMarkPnt3, triMarkPnt1 }, true) }, true));

			PathGeometry triMark5G = new PathGeometry();
			triMark5G.Figures.Add(new PathFigure(triMarkPnt1,
												 new PathSegment[] { new PolyLineSegment(new[] { triMarkPnt2, triMarkPnt3, triMarkPnt1 }, true) }, true));

			TriMarkPath1 = new Path();
			TriMarkPath1.Data = triMark1G;
			TriMarkPath1.StrokeThickness = 0.5;
			TriMarkPath1.Stroke = Brushes.DarkGray;
			TriMarkPath1.Fill = Brushes.LightGray;

			TriMarkPath2 = new Path();
			TriMarkPath2.Data = triMark1G;
			TriMarkPath2.StrokeThickness = 0.5;
			TriMarkPath2.Stroke = Brushes.DarkGray;
			TriMarkPath2.Fill = Brushes.LightGray;

			TriMarkPath3 = new Path();
			TriMarkPath3.Data = triMark1G;
			TriMarkPath3.StrokeThickness = 0.5;
			TriMarkPath3.Stroke = Brushes.DarkGray;
			TriMarkPath3.Fill = Brushes.LightGray;

			TriMarkPath4 = new Path();
			TriMarkPath4.Data = triMark1G;
			TriMarkPath4.StrokeThickness = 0.5;
			TriMarkPath4.Stroke = Brushes.DarkGray;
			TriMarkPath4.Fill = Brushes.LightGray;

			TriMarkPath5 = new Path();
			TriMarkPath5.Data = triMark1G;
			TriMarkPath5.StrokeThickness = 0.5;
			TriMarkPath5.Stroke = Brushes.DarkGray;
			TriMarkPath5.Fill = Brushes.LightGray;

			// --------- 1/5 -----------
			TriMarkTrans1 = new TranslateTransform(0, 0);
			TriMarkPath1.RenderTransform = TriMarkTrans1;

			TriMarkTrans2 = new TranslateTransform(0, 0);
			TriMarkPath2.RenderTransform = TriMarkTrans2;

			TriMarkTrans3 = new TranslateTransform(0, 0);
			TriMarkPath3.RenderTransform = TriMarkTrans3;

			TriMarkTrans4 = new TranslateTransform(0, 0);
			TriMarkPath4.RenderTransform = TriMarkTrans4;

			TriMarkTrans5 = new TranslateTransform(0, 0);
			TriMarkPath5.RenderTransform = TriMarkTrans5;

			TriCanvas.Children.Add(TriMarkPath1);
			TriCanvas.Children.Add(TriMarkPath2);
			TriCanvas.Children.Add(TriMarkPath3);
			TriCanvas.Children.Add(TriMarkPath4);
			TriCanvas.Children.Add(TriMarkPath5);

			TriMarkPath1.SnapsToDevicePixels = true;
			TriMarkPath2.SnapsToDevicePixels = true;
			TriMarkPath3.SnapsToDevicePixels = true;
			TriMarkPath4.SnapsToDevicePixels = true;
			TriMarkPath5.SnapsToDevicePixels = true;
		}

		/// <summary>
		/// Recreate hexagon markers inside hexagon
		/// </summary>
		private void Recreate_ColorMarker()
		{
			EllipseGeometry cElG1 = new EllipseGeometry(new Point(TriCanvas.Width / 2d, TriCanvas.Height / 2d), 4, 4);
			EllipseGeometry cElG2 = new EllipseGeometry(new Point(TriCanvas.Width / 2d, TriCanvas.Height / 2d), 2, 2);
			CombinedGeometry cElCG = new CombinedGeometry(GeometryCombineMode.Exclude, cElG1, cElG2);
			TriColorMark = new Path();
			TriColorMark.Data = cElCG;
			TriColorMark.Fill = new SolidColorBrush(Colors.Black);
			TriCanvas.Children.Add(TriColorMark);

			TriColorMarkTrans = new TranslateTransform();
			TriColorMark.RenderTransform = TriColorMarkTrans;
		}

		/// <summary>
		/// Recreate saturation rectangle
		/// </summary>
		private void Recreate_Saturator(Canvas hoster)
		{
			// possible bounds
			double H = (HexPnt1.X - HexPntC.X) - (HexPnt6.Y - HexPntC.Y);
			SatPnt1 = new Point((int)(HexPntC.X - HexInnerRadius), (int)(HexPnt6.Y + H * 0.55));
			SatPnt2 = new Point((int)(HexPntC.X + HexInnerRadius), (int)(HexPnt6.Y + H * 1.25));

			// geometry
			RectangleGeometry SatG = new RectangleGeometry(new Rect(SatPnt1, SatPnt2));
			SatPath = new Path();
			SatPath.Data = SatG;
			SatPath.StrokeThickness = 0.5;
			SatPath.Stroke = new SolidColorBrush(Colors.Gray);

			SatBrush = new LinearGradientBrush();
			SatPath.Fill = SatBrush;

			hoster.Children.Add(SatPath);
			SatPath.SnapsToDevicePixels = true;

			// line
			LineGeometry SatLineG = new LineGeometry(new Point(SatPnt1.X, SatPnt1.Y - 2), new Point(SatPnt1.X, SatPnt2.Y + 2));
			SatDirectorPath = new Path();
			SatDirectorPath.Data = SatLineG;
			SatDirectorPath.Stroke = new SolidColorBrush(Colors.Black);
			SatDirectorPath.StrokeThickness = 1;
			hoster.Children.Add(SatDirectorPath);
			SatDirectorPath.SnapsToDevicePixels = true;

			SatDirTrans = new TranslateTransform(SatPnt2.X - SatPnt1.X - 1, 0);
			SatDirectorPath.RenderTransform = SatDirTrans;
		}

		/// <summary>
		/// Recreate alphator rectangle
		/// </summary>
		private void Recreate_Alphator(Canvas hoster)
		{
			// possible bounds
			double H = (HexPnt1.X - HexPntC.X) - (HexPntC.Y - HexPnt3.Y);
			AlphPnt1 = new Point((int)(HexPntC.X - HexInnerRadius), (int)(HexPnt3.Y - H * 1.2));
			AlphPnt2 = new Point((int)(HexPntC.X + HexInnerRadius), (int)(HexPnt3.Y - H * 0.6));

			// geometry
			RectangleGeometry AlphG = new RectangleGeometry(new Rect(AlphPnt1, AlphPnt2));
			AlphPath = new Path();
			AlphPath.Data = AlphG;
			AlphPath.StrokeThickness = 0.5;
			AlphPath.Stroke = new SolidColorBrush(Colors.Gray);

			AlphBrush = new LinearGradientBrush();
			AlphPath.Fill = AlphBrush;

			// background with checkmate
			RectangleGeometry AlphGB = new RectangleGeometry(new Rect(AlphPnt1, AlphPnt2));
			AlphPathB = new Path();
			AlphBrushB = new ImageBrush();
			AlphPathB.Data = AlphGB;
			AlphPathB.Fill = AlphBrushB;

			// add background object first, then alpha-filled on top
			hoster.Children.Add(AlphPathB);
			hoster.Children.Add(AlphPath);
			AlphPathB.SnapsToDevicePixels = true;
			AlphPath.SnapsToDevicePixels = true;

			// line
			LineGeometry AlphLineG = new LineGeometry(new Point(AlphPnt1.X, AlphPnt1.Y - 2), new Point(AlphPnt1.X, AlphPnt2.Y + 2));
			AlphDirectorPath = new Path();
			AlphDirectorPath.Data = AlphLineG;
			AlphDirectorPath.Stroke = new SolidColorBrush(Colors.Black);
			AlphDirectorPath.StrokeThickness = 1;
			hoster.Children.Add(AlphDirectorPath);
			AlphDirectorPath.SnapsToDevicePixels = true;

			AlphDirTrans = new TranslateTransform(AlphPnt2.X - AlphPnt1.X - 1, 0);
			AlphDirectorPath.RenderTransform = AlphDirTrans;
		}

		/// <summary>
		/// Redraw main element - color wheel hexagon
		/// </summary>
		/// <param name="hoster"></param>
		private void Redraw_Hexagon(Canvas hoster)
		{
			if (hoster.ActualWidth == 0)
				return;

			WriteableBitmap WB = new WriteableBitmap((int) hoster.ActualWidth, (int) hoster.ActualHeight, 96, 96,
			                                         PixelFormats.Pbgra32, null);
			uint[] pixels = new uint[(WB.PixelWidth * WB.PixelHeight)];

			for (int x = 0; x < WB.PixelWidth; ++x)
			{
				for (int y = 0; y < WB.PixelHeight; ++y)
				{
					int red, green, blue;
					const int alpha = 255;

					int i = (WB.PixelWidth * y + x);

					// general method, but its a wery-wery slow!!
					//if (GearPath.Data.FillContains(new Point(x, y)))

					// this custom method is much faster!!
					if (!PntInCircle(new Point(x, y), HexInnerRadius - 1) && PntInHex(new Point(x, y)))
					{
						double H = GetAngle(new Point(x, y), HexPntC);

						Color C = HSLConverter.HslToRgb(new HSL(H, 1, 0.5));
						red = C.R;
						green = C.G;
						blue = C.B;
					}
					else
					{
						red = green = blue = 0;
					}

					pixels[i] = (uint) ((alpha << 24) + (red << 16) + (green << 8) + blue);
				}
			}

			WB.WritePixels(new Int32Rect(0, 0, WB.PixelWidth, WB.PixelHeight), pixels, WB.PixelWidth * WB.Format.BitsPerPixel / 8, 0);
			ImageBrush IB = new ImageBrush(WB);
			IB.Stretch = Stretch.None;
			IB.TileMode = TileMode.None;
			HexPath.Fill = IB;
		}

		/// <summary>
		/// Redraw 3 markers at bottom of main triangle
		/// </summary>
		private void Redraw_TriangleMarkers()
		{
			double XPos = TriSide / 5d;
			TriMarkTrans3.X = TriColorMarkTrans.X;

			double tmpPos;

			tmpPos = (TriMarkTrans3.X - XPos * 2);
			if (tmpPos < -TriSide / 2d)
				tmpPos = TriSide / 2d + (tmpPos + TriSide / 2d);
			TriMarkTrans1.X = tmpPos;

			tmpPos = (TriMarkTrans3.X - XPos);
			if (tmpPos < -TriSide / 2d)
				tmpPos = TriSide / 2d + (tmpPos + TriSide / 2d);
			TriMarkTrans2.X = tmpPos;

			tmpPos = (TriMarkTrans3.X + XPos);
			if (tmpPos > TriSide / 2d)
				tmpPos = -TriSide / 2d + (tmpPos - TriSide / 2d);
			TriMarkTrans4.X = tmpPos;

			tmpPos = (TriMarkTrans3.X + XPos * 2);
			if (tmpPos > TriSide / 2d)
				tmpPos = -TriSide / 2d + (tmpPos - TriSide / 2d);
			TriMarkTrans5.X = tmpPos;
		}

		/// <summary>
		/// Redraw inner main triangle inside hexagon
		/// </summary>
		private void Redraw_Triangle()
		{
			double angle = GetAngle(HexDirectorPoint, HexPntC);

			WriteableBitmap WB = new WriteableBitmap((int)TriSide, (int)TriHeight, 96, 96, PixelFormats.Pbgra32, null);
			uint[] pixels = new uint[(WB.PixelWidth * WB.PixelHeight)];
			int red = 255;
			int green = 255;
			int blue = 255;
			int alpha = 255;

			Point tp1 = new Point(TriSide / 2d, 0);
			Point tp2 = new Point(0, TriHeight);
			Point tp3 = new Point(TriSide, TriHeight);

			for (int x = 0; x < WB.PixelWidth; ++x)
			{
				for (int y = 0; y < WB.PixelHeight; ++y)
				{
					int i = (WB.PixelWidth * y + x);

					if (PntInTriangle(new Point(x, y), tp1, tp2, tp3))
					{
						Color C = HSLConverter.HslToRgb(new HSL(angle, 1, x / (double)WB.PixelWidth));
						red = C.R;
						green = C.G;
						blue = C.B;
					}

					pixels[i] = (uint)((alpha << 24) + (red << 16) + (green << 8) + blue);
				}
			}
			WB.WritePixels(new Int32Rect(0, 0, WB.PixelWidth, WB.PixelHeight), pixels, WB.PixelWidth * WB.Format.BitsPerPixel / 8, 0);
			TriIBrush.ImageSource = WB;

		}

		/// <summary>
		/// Alternative fill reiangle background via GradientBrush (much faser but less accurate)
		/// </summary>
		private void Redraw_TriangleAlt()
		{
			Color C1 = HSLConverter.HslToRgb(new HSL(H, S, 0));
			Color C11 = HSLConverter.HslToRgb(new HSL(H, S, 0.05));
			Color C2 = HSLConverter.HslToRgb(new HSL(H, S, 0.5));
			Color C22 = HSLConverter.HslToRgb(new HSL(H, S, 0.95));
			Color C3 = HSLConverter.HslToRgb(new HSL(H, S, 1));

			TriBrushAlt.GradientStops.Clear();
			TriBrushAlt.GradientStops.Add(new GradientStop(C1, 0.0));
			TriBrushAlt.GradientStops.Add(new GradientStop(C11, 0.05));
			TriBrushAlt.GradientStops.Add(new GradientStop(C2, 0.5));
			TriBrushAlt.GradientStops.Add(new GradientStop(C22, 0.95));
			TriBrushAlt.GradientStops.Add(new GradientStop(C3, 1.0));
			TriBrushAlt.StartPoint = new Point(0, 0);
			TriBrushAlt.EndPoint = new Point(1, 0);

			TriPath.Fill = TriBrushAlt;
		}

		/// <summary>
		/// Redraw saturator background
		/// </summary>
		private void Redraw_Saturator()
		{
			Color C1 = HSLConverter.HslToRgb(new HSL(H, 0.0, 0.5));
			Color C2 = HSLConverter.HslToRgb(new HSL(H, 0.1, 0.5));
			Color C3 = HSLConverter.HslToRgb(new HSL(H, 0.5, 0.5));
			Color C4 = HSLConverter.HslToRgb(new HSL(H, 0.9, 0.5));
			Color C5 = HSLConverter.HslToRgb(new HSL(H, 1.0, 0.5));

			//SatBrush = new LinearGradientBrush(C1, C2, 0);
			SatBrush.GradientStops.Clear();
			SatBrush.GradientStops.Add(new GradientStop(C1, 0.0));
			SatBrush.GradientStops.Add(new GradientStop(C2, 0.1));
			SatBrush.GradientStops.Add(new GradientStop(C3, 0.5));
			SatBrush.GradientStops.Add(new GradientStop(C4, 0.9));
			SatBrush.GradientStops.Add(new GradientStop(C5, 1.0));
			SatPath.Fill = SatBrush;
		}

		/// <summary>
		/// Redraw background alphator background
		/// </summary>
		private void Redraw_AlphatorB()
		{
			WriteableBitmap WB = new WriteableBitmap((int)(AlphPnt2.Y - AlphPnt1.Y), (int)(AlphPnt2.Y - AlphPnt1.Y), 96, 96, PixelFormats.Pbgra32, null);
			int half = (int)((WB.Height - 1) / 2d);
			uint[] pixels = new uint[(WB.PixelWidth * WB.PixelHeight)];
			int red = 255;
			int green = 255;
			int blue = 255;
			const int alpha = 255;
			for (int x = 0; x < WB.PixelWidth; ++x)
			{
				for (int y = 0; y < WB.PixelHeight; ++y)
				{
					int i = (WB.PixelWidth * y + x);

					if (y <= half)
					{
						red = Colors.Gray.R;
						green = Colors.Gray.G;
						blue = Colors.Gray.B;
					}
					else
					{
						red = Colors.WhiteSmoke.R;
						green = Colors.WhiteSmoke.G;
						blue = Colors.WhiteSmoke.B;						
					}

					//if (x <= half && y <= half)
					//{
					//	red = Colors.LightGray.R;
					//	green = Colors.LightGray.G;
					//	blue = Colors.LightGray.B;
					//}
					//else if (x < half && y > half)
					//{
					//	red = Colors.WhiteSmoke.R;
					//	green = Colors.WhiteSmoke.G;
					//	blue = Colors.WhiteSmoke.B;
					//}
					//else if (x > half && y < half)
					//{
					//	red = Colors.WhiteSmoke.R;
					//	green = Colors.WhiteSmoke.G;
					//	blue = Colors.WhiteSmoke.B;
					//}
					//else if (x > half && y >= half)
					//{
					//	red = Colors.LightGray.R;
					//	green = Colors.LightGray.G;
					//	blue = Colors.LightGray.B;
					//}

					pixels[i] = (uint)((alpha << 24) + (red << 16) + (green << 8) + blue);
				}
			}
			WB.WritePixels(new Int32Rect(0, 0, WB.PixelWidth, WB.PixelHeight), pixels, WB.PixelWidth * WB.Format.BitsPerPixel / 8, 0);
			AlphBrushB.ImageSource = WB;
			AlphBrushB.TileMode = TileMode.Tile;
			AlphBrushB.Viewport = new Rect(0, 0, (AlphPnt2.Y - AlphPnt1.Y) / (AlphPnt2.X - AlphPnt1.X), 1);
		}

		/// <summary>
		/// Redraw alphator background
		/// </summary>
		private void Redraw_Alphator()
		{
			Color C1 = HSLConverter.HslToRgb(new HSL(H, 1, L));
			Color C2 = Color.FromArgb(0, C1.R, C1.G, C1.B);

			AlphBrush.GradientStops.Clear();
			AlphBrush.GradientStops.Add(new GradientStop(C2, 0));
			AlphBrush.GradientStops.Add(new GradientStop(C1, 1));
			AlphPath.Fill = AlphBrush;
		}

		/// <summary>
		/// Call when hexagon director line moving (via mouse etc.) to refresh position of triangle and markers
		/// </summary>
		/// <param name="mousePoint">target point</param>
		/// <param name="sensitiveRadius">ignorable radius near centre to avoid epileptic moves</param>
		private void Move_HexDirector(Point mousePoint, double sensitiveRadius)
		{
			if (!PntInCircle(mousePoint, sensitiveRadius))
			{
				HexDirectorLine.Points[0] = new Point(mousePoint.X + (mousePoint.X - HexPntC.X) * 3, mousePoint.Y + (mousePoint.Y - HexPntC.Y) * 3);
				HexDirectorPoint = HexDirectorLine.Points[0];

				Redraw_TriangleAlt();
				Redraw_Saturator();
				Redraw_Alphator();

				TriCanvasTrans.Angle = GetAngle(HexDirectorPoint, HexPntC) * -1 + 90 ;
				TriCanvasTrans.CenterX = TriCanvas.Width / 2d;
				TriCanvasTrans.CenterY = TriCanvas.Height / 2d;

				_h = GetAngle(HexDirectorLine.Points[0], HexPntC);
			}
		}

		/// <summary>
		/// Set angle on hexagon for hexagon director line
		/// </summary>
		/// <param name="gradHue">Angle in grad</param>
		/// <param name="origin">Custom origin point</param>
		/// <returns>Target triangle point to set hex director line</returns>
		private void Move_HexDirector(double gradHue, Point origin)
		{
			if (gradHue < 0)
				gradHue = -gradHue;
			
			if (gradHue > 360)
				gradHue = gradHue % 360;

			if (gradHue == 0 || gradHue == 360)
			{
				Move_HexDirector(new Point(origin.X + origin.X, origin.Y), HexInnerRadius);
				return;
			}
			if (gradHue == 90)
			{
				Move_HexDirector(new Point(origin.X, origin.Y - origin.Y), HexInnerRadius);
				return;
			}
			if (gradHue == 180)
			{
				Move_HexDirector(new Point(origin.X - origin.X, origin.Y), HexInnerRadius);
				return;
			}
			if (gradHue == 270)
			{
				Move_HexDirector(new Point(origin.X, origin.Y + origin.Y), HexInnerRadius);
				return;
			}
			
			if (gradHue == 45)
			{
				Move_HexDirector(new Point(origin.X + origin.X, origin.Y - origin.Y), HexInnerRadius);
				return;
			}
			if (gradHue == 135)
			{
				Move_HexDirector(new Point(origin.X - origin.X, origin.Y - origin.Y), HexInnerRadius);
				return;
			}
			if (gradHue == 225)
			{
				Move_HexDirector(new Point(origin.X - origin.X, origin.Y + origin.Y), HexInnerRadius);
				return;
			}
			if (gradHue == 315)
			{
				Move_HexDirector(new Point(origin.X + origin.X, origin.Y + origin.Y), HexInnerRadius);
				return;
			}

			double sideB = origin.X;
			double rad = gradHue * (Math.PI / 180d);
			double sideA = sideB * Math.Tan(rad);

			if (gradHue > 0 && gradHue < 45)
			{
				Move_HexDirector(new Point(origin.X + sideB, origin.Y - sideA), HexInnerRadius);
				return;
			}
			if (gradHue > 45 && gradHue < 90)
			{
				gradHue = 90 - gradHue;
				rad = gradHue * (Math.PI / 180d);
				sideA = sideB * Math.Tan(rad);
				Move_HexDirector(new Point(origin.X + sideA, origin.Y - sideB), HexInnerRadius);
				return;
			}
			if (gradHue > 90 && gradHue < 135)
			{
				gradHue = gradHue - 90;
				rad = gradHue * (Math.PI / 180d);
				sideA = sideB * Math.Tan(rad);
				Move_HexDirector(new Point(origin.X - sideA, origin.Y - sideB), HexInnerRadius);
				return;
			}
			if (gradHue > 135 && gradHue < 180)
			{
				gradHue = 180 - gradHue;
				rad = gradHue * (Math.PI / 180d);
				sideA = sideB * Math.Tan(rad);
				Move_HexDirector(new Point(origin.X - sideB, origin.Y - sideA), HexInnerRadius);
				return;
			}
			if (gradHue > 180 && gradHue < 225)
			{
				gradHue = gradHue - 180;
				rad = gradHue * (Math.PI / 180d);
				sideA = sideB * Math.Tan(rad);
				Move_HexDirector(new Point(origin.X - sideB, origin.Y + sideA), HexInnerRadius);
				return;
			}
			if (gradHue > 225 && gradHue < 270)
			{
				gradHue = 270 - gradHue;
				rad = gradHue * (Math.PI / 180d);
				sideA = sideB * Math.Tan(rad);
				Move_HexDirector(new Point(origin.X - sideA, origin.Y + sideB), HexInnerRadius);
				return;
			}
			if (gradHue > 270 && gradHue < 315)
			{
				gradHue = gradHue - 270;
				rad = gradHue * (Math.PI / 180d);
				sideA = sideB * Math.Tan(rad);
				Move_HexDirector(new Point(origin.X + sideA, origin.Y + sideB), HexInnerRadius);
				return;
			}
			if (gradHue > 315 && gradHue < 360)
			{
				gradHue = 360 - gradHue;
				rad = gradHue * (Math.PI / 180d);
				sideA = sideB * Math.Tan(rad);
				Move_HexDirector(new Point(origin.X + sideB, origin.Y + sideA), HexInnerRadius);
				return;
			}

			throw new Exception("Impossiburu angle! Angle must be [0..360]");
		}

		/// <summary>
		/// Lightness (triangle) mouse move
		/// </summary>
		/// <param name="mousePoint">target point</param>
		private void Move_TriColorMark(Point mousePoint)
		{
			if (PntInTriangle(mousePoint, TriPnt1, TriPnt2, TriPnt3)) // mouse inside
			{
				TriColorMarkTrans.X = mousePoint.X - TriCanvas.Width / 2d;
				TriColorMarkTrans.Y = mousePoint.Y - TriCanvas.Height / 2d;
			}
			else if (mousePoint.Y > TriPnt2.Y) // mouse bottom
			{
				if (mousePoint.X < TriPnt2.X)
					TriColorMarkTrans.X = TriPnt2.X - TriCanvas.Width / 2d;
				else if (mousePoint.X > TriPnt3.X)
					TriColorMarkTrans.X = TriPnt3.X - TriCanvas.Width / 2d;
				else
					TriColorMarkTrans.X = mousePoint.X - TriCanvas.Width / 2d;

				TriColorMarkTrans.Y = TriPnt2.Y - TriCanvas.Height / 2d;
			}
			else if (mousePoint.X > TriPnt1.X) // mouse righter
			{
				if (mousePoint.Y < TriPnt1.Y)
				{
					TriColorMarkTrans.X = TriPnt1.X - TriCanvas.Width / 2d;
					TriColorMarkTrans.Y = TriPnt1.Y - TriCanvas.Height / 2d;
				}
				else
				{
					double tmpside = (mousePoint.Y - TriPnt1.Y) / (Math.Sqrt(3d) / 2d);
					TriColorMarkTrans.X = (TriPnt1.X + (tmpside / 2d)) - TriCanvas.Width / 2d;
					TriColorMarkTrans.Y = mousePoint.Y - TriCanvas.Height / 2d;
				}
			}
			else if (mousePoint.X < TriPnt1.X) // mouse lefter
			{
				if (mousePoint.Y < TriPnt1.Y)
				{
					TriColorMarkTrans.X = TriPnt1.X - TriCanvas.Width / 2d;
					TriColorMarkTrans.Y = TriPnt1.Y - TriCanvas.Height / 2d;
				}
				else
				{
					double tmpside = (mousePoint.Y - TriPnt1.Y) / (Math.Sqrt(3d) / 2d);
					TriColorMarkTrans.X = (TriPnt1.X - (tmpside / 2d)) - TriCanvas.Width / 2d;
					TriColorMarkTrans.Y = mousePoint.Y - TriCanvas.Height / 2d;
				}
			}

			double P = TriColorMarkTrans.X + TriSide / 2d;
			_l = (P / (TriSide / 100d)) / 100d;
		}

		/// <summary>
		/// Lightness (triangle) editor set by value
		/// </summary>
		/// <param name="value">value of lightness [0..1]</param>
		private void Move_TriColorMark(double value)
		{
			double P = (TriSide * value) - TriSide / 2d;
			double tmp = value > 0.5 ? 1 - value : value;
			TriColorMarkTrans.X = P;
			TriColorMarkTrans.Y = (TriCanvas.Height - TriHeight) - TriHeight * tmp;
		}

		/// <summary>
		/// Satiration editor mouse move
		/// </summary>
		/// <param name="mousePoint">target point</param>
		private void Move_SatDirector(Point mousePoint)
		{
			// do -1 pixel to avoid WPF antialiasing mistakes
			double targetX = mousePoint.X;
			if (targetX < SatPnt1.X)
				targetX = SatPnt1.X;
			if (targetX > SatPnt2.X - 1)
				targetX = SatPnt2.X - 1;
			SatDirTrans.X = targetX - SatPnt1.X;

			_s = (targetX - SatPnt1.X) / (SatPnt2.X - 1 - SatPnt1.X);
			Redraw_TriangleAlt();
		}

		/// <summary>
		/// Satiration editor set by value
		/// </summary>
		/// <param name="value">value of saturation [0..1]</param>
		private void Move_SatDirector(double value)
		{
			double targetX = SatPnt1.X + ((SatPnt2.X - SatPnt1.X) * value);

			// do -1 pixel to avoid WPF antialiasing mistakes
			if (targetX < SatPnt1.X)
				targetX = SatPnt1.X;
			if (targetX > SatPnt2.X - 1)
				targetX = SatPnt2.X - 1;
			SatDirTrans.X = targetX - SatPnt1.X;

			Redraw_TriangleAlt();
		}

		/// <summary>
		/// Alpha editor mouse move
		/// </summary>
		/// <param name="mousePoint">target point</param>
		private void Move_AlphDirector(Point mousePoint)
		{
			// do -1 pixel to avoid WPF antialiasing mistakes
			double targetX = mousePoint.X;
			if (targetX < AlphPnt1.X)
				targetX = AlphPnt1.X;
			if (targetX > AlphPnt2.X - 1)
				targetX = AlphPnt2.X - 1;
			AlphDirTrans.X = targetX - AlphPnt1.X;

			if (mousePoint.X - AlphPnt1.X > 0)
				_a = (byte)(255d / ((AlphPnt2.X - AlphPnt1.X - 1) / (targetX - AlphPnt1.X)));
			else
				_a = 0;
		}

		/// <summary>
		/// Alpha editor set by value
		/// </summary>
		/// <param name="value">alpha value [0..255]</param>
		private void Move_AlphDirector(byte value)
		{
			double targetX = AlphPnt1.X + ((AlphPnt2.X - AlphPnt1.X) * (value / 255d));

			// do -1 pixel to avoid WPF antialiasing mistakes
			if (targetX < AlphPnt1.X)
				targetX = AlphPnt1.X;
			if (targetX > AlphPnt2.X - 1)
				targetX = AlphPnt2.X - 1;
			AlphDirTrans.X = targetX - AlphPnt1.X;
		}

		#region Local helpers

		/// <summary>
		/// Get an angle of the point for hexagon color wheel in range 0-1
		/// </summary>
		/// <param name="point">Target point</param>
		/// <param name="origin">Custom origin point</param>
		/// <returns></returns>
		private static double GetAngle(Point point, Point origin)
		{
			double x = (point.X - origin.X);
			double y = -(point.Y - origin.Y);
			double angle = Math.Atan2(y, x) * (180 / Math.PI);
			
			if (angle >= 360)
				angle = 360 - angle;

			if (angle < 0)
				angle = 360 + angle;

			//angle /= 360;
			return angle;
		}

		/// <summary>
		/// Target point is inside a triangle?
		/// </summary>
		/// <param name="srcP">Target point</param>
		/// <param name="p1">Triangle point 1</param>
		/// <param name="p2">Triangle point 1</param>
		/// <param name="p3">Triangle point 1</param>
		/// <returns></returns>
		private static bool PntInTriangle(Point srcP, Point p1, Point p2, Point p3)
		{
			double t1 = ((p1.X - srcP.X) * (p2.Y - p1.Y) - (p2.X - p1.X) * (p1.Y - srcP.Y));
			double t2 = ((p2.X - srcP.X) * (p3.Y - p2.Y) - (p3.X - p2.X) * (p2.Y - srcP.Y));
			double t3 = ((p3.X - srcP.X) * (p1.Y - p3.Y) - (p1.X - p3.X) * (p3.Y - srcP.Y));
			return ((t1 >= 0 && t2 >= 0 && t3 >= 0) || (t1 <= 0 && t2 <= 0 && t3 <= 0));
		}

		/// <summary>
		/// Target point is inside a circle?
		/// </summary>
		/// <param name="srcP">Target point</param>
		/// <param name="radius">Radius of circle</param>
		/// <returns></returns>
		private bool PntInCircle(Point srcP, double radius)
		{
			return (srcP.X - HexPntC.X) * (srcP.X - HexPntC.X) + (srcP.Y - HexPntC.Y) * (srcP.Y - HexPntC.Y) <= radius * radius;
		}

		/// <summary>
		/// Target point is inside a hexagon?
		/// </summary>
		/// <param name="srcP">Target point</param>
		/// <returns></returns>
		private bool PntInHex(Point srcP)
		{
			// entend hexagon by 1 pixel to avoid antialiasing problems
			Point HP1 = new Point(HexPnt1.X + 1, HexPnt1.Y);
			Point HP2 = new Point(HexPnt2.X + 1, HexPnt2.Y - 1);
			Point HP3 = new Point(HexPnt3.X - 1, HexPnt3.Y - 1);
			Point HP4 = new Point(HexPnt4.X - 1, HexPnt4.Y);
			Point HP5 = new Point(HexPnt5.X - 1, HexPnt5.Y + 1);
			Point HP6 = new Point(HexPnt6.X + 1, HexPnt6.Y + 1);

			if (srcP.Y < HP2.Y || srcP.Y > HP5.Y)
				return false;

			if (srcP.X >= HP3.X && srcP.X <= HP2.X && srcP.Y >= HP3.Y && srcP.Y < HP5.Y)
				return true;

			if (srcP.Y <= HexPntC.Y)
			{
				if (srcP.X <= HP3.X)
				{
					if (PntInTriangle(srcP, HexPntC, HP3, HP4)) return true;
					
				}
				else if (srcP.X >= HP2.X)
				{
					if (PntInTriangle(srcP, HexPntC, HP1, HP2)) return true;
				}
			}
			else
			{
				if (srcP.X <= HP5.X)
				{
					if (PntInTriangle(srcP, HexPntC, HP4, HP5)) return true;
				}
				else if (srcP.X >= HP6.X)
				{
					if (PntInTriangle(srcP, HexPntC, HP6, HP1)) return true;
				}
			}
			return false;
		}

		#endregion

	}
}
